package com.lonefennec.chameleon;

import java.util.Set;

import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactFilter;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.ContactListener;
import com.badlogic.gdx.physics.box2d.Filter;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.physics.box2d.Manifold;
import com.badlogic.gdx.physics.box2d.PolygonShape;
import com.badlogic.gdx.physics.box2d.World;
import com.lonefennec.chameleon.characters.WalkingActor;

public abstract class Physics
{
	private static World world;
	private static Box2DDebugRenderer debugRenderer;
	
	public static final float WORLD_TO_SCREEN = 100.0f;
	public static final float SCREEN_TO_WORLD = 0.01f;
	
	public static void initialize()
	{
		debugRenderer = new Box2DDebugRenderer();
		world = new World(new Vector2(0, -20), true);
		
		world.setContactListener(contactListener);
		world.setContactFilter(new PlatformerContactFilter());
	}
	
	public static void dispose()
	{
		debugRenderer.dispose();
		world.dispose();
	}
	
	public static void simulate(float delta)
	{
		world.step(delta, 6, 2);
	}
	
	public static void draw(Matrix4 projMatrix)
	{
		debugRenderer.render(world, projMatrix);
	}

	public static World getWorld()
	{
		return world;
	}
	
	private static class PlatformerContactFilter implements ContactFilter
	{
		@Override
		public boolean shouldCollide(Fixture fixtureA, Fixture fixtureB)
		{
			Object objA = fixtureA.getBody().getUserData();
			Object objB = fixtureB.getBody().getUserData();
			
			//walking actors don't collide with each other
			boolean result = !(objA instanceof WalkingActor
					&& objB instanceof WalkingActor);
			
			return result;
		}
	}
	
	private static class WalkActorContactListener implements ContactListener
	{
		void updateFootCollisions(Fixture fixture, int change)
		{
			WalkingActor ent = (WalkingActor) fixture.getBody().getUserData();

			if (ent != null)
			{
				if (fixture == ent.getFootFixture())
					ent.setFootCollisionCount(ent.getFootCollisionCount() + change);
			}
		}

		boolean isFootFixture(Fixture fixture)
		{
			Object obj = fixture.getBody().getUserData();
			
			if (obj instanceof WalkingActor)
			{
				return fixture == ((WalkingActor) obj).getFootFixture();
			}
			else
				return false;
		}

		boolean isLeftAttackSensor(Fixture fixture)
		{
			Object obj = fixture.getBody().getUserData();
			
			if (obj instanceof WalkingActor)
			{
				return fixture == ((WalkingActor) obj).getLeftFixture();
			}
			else
				return false;
		}

		boolean isRightAttackSensor(Fixture fixture)
		{
			Object obj = fixture.getBody().getUserData();
			
			if (obj instanceof WalkingActor)
			{
				return fixture == ((WalkingActor) obj).getRightFixture();
			}
			else
				return false;
		}

		void updateAttackCollisions(Fixture fixture, Fixture contactFixture, boolean beginning, boolean left)
		{
			WalkingActor ent = (WalkingActor) fixture.getBody().getUserData();
			WalkingActor contactedEnt = (WalkingActor) contactFixture.getBody().getUserData();

			Set<WalkingActor> collisionSet = left ? ent.getLeftCollidingActors() : ent.getRightCollidingActors();

			if (beginning)
			{
				collisionSet.add(contactedEnt);
			}
			else
			{
				collisionSet.remove(contactedEnt);
			}
		}

		public void doContactChange(Contact contact, boolean beginning)
		{
			Fixture a = contact.getFixtureA();
			Fixture b = contact.getFixtureB();

			boolean isFootA = isFootFixture(a);
			boolean isFootB = isFootFixture(b);
			boolean isLAttackA = isLeftAttackSensor(a);
			boolean isLAttackB = isLeftAttackSensor(b);
			boolean isRAttackA = isRightAttackSensor(a);
			boolean isRAttackB = isRightAttackSensor(b);

			boolean isSensorA = (isFootA || isLAttackA || isRAttackA);
			boolean isSensorB = (isFootB || isLAttackB || isRAttackB);

			int footChange = beginning ? 1 : -1;

			if (!(isSensorA && isSensorB))
			{
				if (isFootA)
					updateFootCollisions(a, footChange);
				else if (isLAttackA)
					updateAttackCollisions(a, b, beginning, true);
				else if (isRAttackA)
					updateAttackCollisions(a, b, beginning, false);

				if (isFootB)
					updateFootCollisions(b, footChange);
				else if (isLAttackB)
					updateAttackCollisions(b, a, beginning, true);
				else if (isRAttackB)
					updateAttackCollisions(b, a, beginning, false);
			}
		}

		@Override
		public void beginContact(Contact contact)
		{
			doContactChange(contact, true);
		}

		@Override
		public void endContact(Contact contact)
		{
			doContactChange(contact, false);
		}

		@Override
		public void preSolve(Contact contact, Manifold oldManifold)
		{
		}

		@Override
		public void postSolve(Contact contact, ContactImpulse impulse)
		{
		}
	}
	
	public static void createFootFixture(WalkingActor actor, Body toBody, float bodyWidth, float bodyHeight, float bottom)
	{		
		final float FOOT_SIZE = 0.01f;
		
		FixtureDef fixtureDef = new FixtureDef();
		
		PolygonShape footShape = new PolygonShape();
		footShape.setAsBox(bodyWidth/2, FOOT_SIZE, new Vector2(0, bottom + FOOT_SIZE), 0);
		
		fixtureDef.isSensor = true;
		fixtureDef.shape = footShape;
		
		Fixture fixture = toBody.createFixture(fixtureDef);
		
		Filter filter = fixture.getFilterData();
		filter.groupIndex = -2;
		fixture.setFilterData(filter);
		
		actor.setFootFixture(fixture);		
	}
	
	private static ContactListener contactListener = new WalkActorContactListener();
}
